/***************************************************************************
 *   Copyright (C) 2008 by Ivan Blagoev Topolsky   *
 *   ivan.topolsky@genebio.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 3 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>

// BOOST
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
//#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>

// windows' minmax conflict with STL's this will avoid it.
#ifndef NOMINMAX 
#define NOMINMAX 1
#endif

// OLE/COM support
#include "comutil.h"
#include "disphelper.h"

#include "arthur.h"


using namespace std;	// STL
using namespace xeno;	// our helpers
using namespace xeno::merlin;

#define DEFAULT_EXTENSION ".bol"


// parser for '@responsefile' and ':num'
pair<string, string> at_option_parser(string const&s) {
	switch(s[0]) {
		case '@':
			return std::make_pair(string("response-file"), s.substr(1));
		case ':':
			return std::make_pair(string("refid"), s.substr(1));
		default:
			return pair<string, string>();
	}
}




int main (int argc, char *argv[]) {
	cout << "Merlin welcomes you !" << endl << endl;

	/*******************
	*                  *
	*   COMMAND LINE   *
	*                  *
	*******************/
	
	string ifname ("");
	string ofname ("");
	string runname ("");
	ExtractList reflist;
	bool skipms1 = false, skipms2 = false;
	bool dropms1 = false, dropms2 = false;
	bool allatonce = false;
	
	using namespace boost;
	using namespace boost::program_options;
	
	// list the '#Fields' 
	stringstream fields;
	fields << "which informations to use as run description.\n"
		"Fields begin #, all other caracters are passed as-is\n"
		"Accepted #Fields :\n  \t";
	arthur::printhelp(fields);
	fields << "Default :\n  \t<" << DEFAULT_RUNNAME_TEMPLATE ">";
	
	// Declare the supported options.
	options_description desc("Usage:\n\tmerlinbol [ --options [...]] [input file] [output file]\n\nAllowed options");
	desc.add_options()
		("help,h", "produce help message")
		("input-file,i", value< string >(&ifname), "input file name")
		("output-file,o", value< string >(&ofname), "output file name")
		("sample-info",	"print out information about sample only")
		("load-all",	"attempts to load all spectra at once.\n"
				"Much faster than processing each filter but eats memory like candy.")
//		("centroid,c", "do centroid spectra")
		("skip-peak", value< string >(), "choose which peaks to skip : none, ms1, ms2 or all")
		("skip-all", value< string >(), "choose which collection to skip completely (peaks and structure) : none, ms1, ms2 or all")
		("runname,r", value< string >(&runname), fields.str().c_str())
		("refid,:", value<ExtractList>(&reflist), "only extract peak data for those specific IDs\ncan be specified with ':num', too")
		("response-file,@", value<string>(), "file containing additional options\ncan be specified with '@name', too")
		;
	
	positional_options_description pos;
	pos.add("input-file", 1);
	pos.add("output-file", 1);

	variables_map vm;

	// Parse the command line
	try {
		store(command_line_parser(argc, argv).options(desc).positional(pos).extra_parser(at_option_parser).run(), vm);
		notify(vm);
	} catch (exception& e) {
		cerr	<< "Error while parsing options :" << endl
			<< e.what() << endl;
		exit(3);
	} catch (...) {
		cerr	<< "Error while parsing options." << endl;
		exit(3);
	}


	// Check the options
	if (vm.count("response-file")) {
		// allow for several response files
		string rf = vm["response-file"].as<string>();
		try {
			cout << "reading options from " << rf << "...";

			// Load the file and tokenize it
			ifstream ifs(rf.c_str());
			if (!ifs) 
				throw string ("Could no open the response file\n");

			// Read the whole file into a string
			stringstream ss;
			ss << ifs.rdbuf();
			string data (ss.str());
			
			// Split the file content
			vector<string> args;
			split(args, data, is_space());

			// Parse the file and store the options
			store(command_line_parser(args).options(desc).extra_parser(at_option_parser).run(), vm);
			notify(vm);

			cout << "done." << endl;
		} catch(string err) {
			cerr << err << endl;
			exit(3);
		} catch  (exception& e) {
			cerr	<< "Sorry, couldn't parse the response file :" << endl
				<< e.what() << endl;
			exit(3);
		} catch (...) {
			cerr << "Sorry, couldn't parse the response file" << endl;
			exit(3);
		}
	}

	if (vm.count("help")) {
		cout << desc << endl;
		exit(0);
	}

	if (vm.count("refid")) {
		cout << reflist.size() << " specific spectra to be extracted" << endl;
		// by default : force the peaks of the selected spectra
		// and skip all other peaks
		skipms1 = skipms2 = true;
#if 0
		for(int i = 0; i < reflist.size(); i++) {
			cout << reflist[i] << "\t";
		}
#endif
	}

	// BUG Can't get centroiding to work with this version of API
#if 0
	if (vm.count("centroid")) {
		Centroiding = true;
		cout << "centroiding enabled" << endl;
	}
#endif

	if (vm.count("skip-all")) {
		string skipall(vm["skip-all"].as<string>());
		dropms1 = ((skipall == "ms1") || (skipall == "all")) ? true : false;
		dropms2 = ((skipall == "ms2") || (skipall == "msn") || (skipall == "all")) ? true : false;
		cout	<< "All data for MS :\t" << (dropms1 ? "skip" : "fetch") << endl
			<< "All data for MS^n :\t" << (dropms2 ? "skip" : "fetch") << endl;
	}
	if (vm.count("skip-peak")) {
		string skippeak (vm["skip-peak"].as<string>());
		skipms1 = ((skippeak == "ms1") || (skippeak == "all")) ? true : false;
		skipms2 = ((skippeak == "ms2") || (skippeak == "msn") || (skippeak == "all")) ? true : false;
		cout	<< "Peak data for MS :\t" << ((skipms1 || dropms1) ? "skip" : "fetch") << endl
			<< "Peak data for MS^n :\t" << ((skipms2 || dropms2) ? "skip" : "fetch") << endl;
	}
	
	if (vm.count("load-all")) {
		allatonce = true;
		cout << "WARNING: loading all spectra at once. make sure you have enough memory. and be patient." << endl;
	}

	if (! vm.count("input-file")) {
		cerr << "Please at least give an input file name" << endl
				<< endl << desc << endl;
		exit(3);
	}

	if (! vm.count("output-file")) {
		using namespace boost::regex_constants;
		
		static const regex rx_remove_extension("\\.[^./\\\\]*$", perl);
		ofname = regex_replace(ifname,rx_remove_extension,"", match_default) + DEFAULT_EXTENSION;
	}


	/*********************
	*                    *
	*   INITIALISATION   *
	*                    *
	*********************/

	//
	// Never forget to initialise first otherwise "800401f0 - CoInitialize has not been called."
 	//
	switch (CoInitialize(NULL)) {
		case S_OK:
			cout << "COM initialised" << endl;
			break;
		case S_FALSE:
			cout << "COM already initialised in this thread" << endl;
			break;
#ifdef RPC_E_CHANGED_MOD
		case RPC_E_CHANGED_MOD:
			cerr << "different concurrency model  or neutral threaded apartment" << endl;
			exit(2);
#endif
		case E_INVALIDARG:
			cerr << "invalid arg" << endl;
			exit(2);
		case E_OUTOFMEMORY:
			cerr << "out of memory" << endl;
			exit(2);
		case E_UNEXPECTED:
			cerr << "unexpected error" << endl;
			exit(2);
		default:
			cerr << "unkown COM initialisation failure" << endl;
			exit(2);
	};
	
	
	dhInitialize(FALSE);

	
// for debugging purpose :
//	dhToggleExceptions(TRUE);	// this will cause error to make nice messages boxes



	/***************
	*              *
	*   GET DATA   *
	*              *
	***************/
	
	bol::RunLcmsms *run;

	{
		arthur reader;
		
		// init the reader, and open the file
		reader.initfile(ifname.c_str());
		
		// get information about sample
		if (vm.count("sample-info")) {
			reader.samplelist(NULL);
			dhUninitialize(TRUE);
			exit(0);
		} else {
			if (vm.count("runname")) 
				reader.samplelist(&runname);
			else 
				reader.samplelist();
		}
		
		// read the file
		reader.dosample(
			allatonce, skipms1,skipms2,dropms1,dropms2,
			reflist.size() ? &reflist : NULL);
		
		// create links between fragment and precusros
		reader.buildptr();
		
		// get data
		run = reader.getRun();
	}
	// all instances of COM objects are handled with smart pointer and should automatically collect garbage
	
	// close DispHelper driver
	// and shutdown OLE/COM service
	dhUninitialize(TRUE);

	{
		const char *cptr = ofname.c_str();
		cout << endl << "Writing to : " << cptr << " ..." << endl;

#ifdef HAVE_PROTOBUF
		bol::protobuf::RunLcmsms pb;
	
		// operator declared in the include file will take care of populating the google protobuf serializer
		cout << "serializing...";
		pb << *run;
		delete run;
		cout << "done" << endl;
		
		// save data to archive
		cout << "writing...";
		fstream os(cptr, ios::out | ios::trunc | ios::binary);	
	
		if (!pb.SerializeToOstream(&os)) {
			cerr << "Failed to serialize data." << endl;
		} else {
			cout << "done." << endl;
		}
#else
#error		"google protocole buffer serialisation isn't compiled in"
#endif
	}
	// to manually inspect output :
	//  protoc --decode=bol.protobuf.RunLcmsms runlcmsms.proto < {ofname}  | less

	cout << endl << "Finished." << endl << endl;
	exit(0);
}
